#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import logging
from ctools.logext import *

_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

class TNode(object):
    def __init__(self, name='', data=None, parent=None):
        self._keyname = name
        self.data = data

        self._children = []

        if parent is None:
            parent = self
        if not isinstance(parent, TNode):
            raise ValueError('parent must be a TNode object')
        self._parent = parent

    def __str__(self):
        return f'{self._keyname}'

    def __repr__(self):
        return f'{self.__class__.__name__}({self._keyname})'

    def __len__(self):
        '''number of child node'''
        return len(self._children)

    def __bool__(self):
        '''return True for exist node'''
        return True

    def __iter__(self):
        return iter(self._children)

    def __next__(self):
        # for node in self._children:
        #     yield node
        return self._children.__next__()

    def __getitem__(self, index):
        return self._children[index]

    def __contains__(self, item):
        return item in self._children

    @property
    def name(self):
        return self._keyname

    def set_name(self, name:str):
        self._keyname = name

    @property
    def level(self):
        '''root --level--> thisnode'''
        level = 0
        node = self
        while not node.is_root:
            node = node.parent
            level += 1
        return level

    @property
    def depth(self):
        '''thisnode --depth--> leaf'''
        if len(self._children) == 0:
            return 1
        return max([child.depth for child in self._children]) + 1
        # def _calc_depth(node):
        #     _depth = 0
        #     for child in node:
        #         tmp = _calc_depth(child)
        #         if tmp > _depth:
        #             _depth = tmp
        #     return _depth + 1

        # return _calc_depth(self)

    def path(self, parent=None):
        # FIXME:
        path = []
        node = self
        while True:
            path.append(node._keyname)
            if node == parent or node.is_root:
                break
            node = node.parent
        return path[::-1]

    @property
    def children(self):
        return self._children

    @property
    def parent(self):
        return self._parent

    def set_parent(self, node):
        if not isinstance(node, (TNode, type(None))):
            raise ValueError('parent must be a TNode object')
        self._parent = node

    @property
    def root(self):
        node = self
        while not node.is_root:
            node = node.parent
        return node

    @property
    def is_root(self):
        return (self._parent == self)

    def make_self_root(self):
        self._parent = self
        return self

    def add_child(self, node=None):
        '''add a child node to current node'''
        if not isinstance(node, TNode):
            raise ValueError('child must be a TNode object')
        node.set_parent(self)
        self._children.append(node)
        return node

    def rm_item(self, node):
        if not isinstance(node, TNode):
            raise ValueError('child must be a TNode object')
        if node in self._children:
            self._children.remove(node)

    def find(self, func=lambda x: False, check_self=True):
        def _find(node):
            while len(node._children) == 0:
                if func(node):
                    return node
                _node = _find(node)
                if _node is not None:
                    return _node
            return None

        if check_self:
            node = func(self)
            if node is not None:
                return node

        return _find(self)

    def iter_nodes(self, func, args={}, depth=0):
        func(self, args, depth)
        for node in self._children:
            node.iter_nodes(func, args, depth+1)

    def iter_nodes_reverse(self, func, args={}, depth=0):
        tmp_items = self._children.copy()
        tmp_items.reverse()

        func(self, args, depth)
        for node in tmp_items:
            node.iter_nodes_reverse(func, args, depth+1)

    def dump_reverse_to_lines(self, prefix='', pad='|   ') -> list[str]:
        def func(node, args, depth):
            _prefix = args['pad'] * depth
            args['lines'].append(f'{_prefix}+-- {node}')

        args = {
            'pad': pad,
            'lines': [],
        }
        self.iter_nodes_reverse(func, args)
        return args['lines']

    def dump_reverse(self, prefix='', pad='|   '):
        lines = self.dump_reverse_to_lines(prefix, pad)
        max_len = max([len(line) for line in lines])
        name_width = max_len - (self.depth * 4)

        '''
        +-- root
        |   +-- node0
        |   |   +-- node0-0
        |   |   +-- node0-1
        |   +-- node1
        |   |   +-- node1-0
        |   |   +-- node1-1

        node0-0 --+   |   |
        node0-1 --+   |   |
              node0 --+   |
        node1-0 --+   |   |
        node1-1 --+   |   |
              node1 --+   |
                   root --+
        '''

        lines.reverse()
        for line in lines:
            level = line.count('|')
            name_pos = line.find('+-- ') + 4
            name = line[name_pos:]   #.ljust(name_width)
            left_str = prefix + '    ' * (self.depth - level - 1)
            right_str = '   |' * level
            print(f'{left_str}{name:>{name_width}} --+{right_str}')

    def dump_to_lines(self, prefix='', pad='|   ') -> list[str]:
        def func(node, args, depth):
            _prefix = prefix + args['pad'] * depth
            args['lines'].append(f'{_prefix}+-- {node}')

        args = {
            'pad': pad,
            'lines': [],
        }
        self.iter_nodes(func, args)
        return args['lines']

    def dump(self, prefix='', pad='|   '):
        lines = self.dump_to_lines(prefix, pad)
        print('\n'.join(lines))
